package com.hdax.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hdax.gulimall.product.service.CategoryBrandRelationService;
import com.hdax.gulimall.product.vo.Catalog2Vo;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hdax.common.utils.PageUtils;
import com.hdax.common.utils.Query;

import com.hdax.gulimall.product.dao.PmsCategoryDao;
import com.hdax.gulimall.product.entity.PmsCategoryEntity;
import com.hdax.gulimall.product.service.PmsCategoryService;


@Service("pmsCategoryService")
public class PmsCategoryServiceImpl extends ServiceImpl<PmsCategoryDao, PmsCategoryEntity> implements PmsCategoryService {

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;


    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<PmsCategoryEntity> page = this.page(
                new Query<PmsCategoryEntity>().getPage(params),
                new QueryWrapper<PmsCategoryEntity>()
        );

        return new PageUtils(page);
    }


    /**
     * 查询所有分类，以树形结构组装
     * @return
     */
    @Override
    public List<PmsCategoryEntity> listWithTree() {
        //1、首先查询所有的分类
        List<PmsCategoryEntity> pmsCategoryEntities = baseMapper.selectList(null);

        //组装成父子的树形结构
        //2、通过所有的分类中过滤找到所有的一级分类
        List<PmsCategoryEntity> cateOne = pmsCategoryEntities.stream().filter(pmsCategory->
             pmsCategory.getParentCid() == 0
        ).map((menu)->{
            //当前menu遍历的是所有的一级分类，往每个一级分类中写入二级分类
            //两个参数为，第一个：一级分类，第二个是所有的分类
            //循环一级分类的类别，根据一级分类找到对应的二级分类
            menu.setChildren(getChildrens(menu,pmsCategoryEntities));
            return menu;
        }).sorted((menu1,menu2)->{
            return( menu1.getSort()==null?0: menu1.getSort())-(menu2.getSort()==null?0:menu2.getSort());
        }).collect(Collectors.toList());




        return cateOne;
    }


    /**
     * 删除
     * @param asList
     */
    @Override
    public void removeMenuByIds(List<Long> asList) {


        //TODO 检查当前删除的菜单，是否被别的地方引用
        //逻辑删除
        //批量删除
        baseMapper.deleteBatchIds(asList);
    }

    @Override
    public Long[] findCatelogPath(Long attrGroupId1) {
        List<Long> ids=new ArrayList<>();
        //                                        255
        List<Long> parentPath = findParentPath(attrGroupId1, ids);
        //逆序转换回来
        Collections.reverse(parentPath);
        return (Long[]) ids.toArray(new Long[parentPath.size()]);
    }


    /**
     * 更新分类的数据，和分类关联表的数据
     * @param pmsCategory
     */
    //第一个参数是删除区域的key，
    //修改完成之后，删除缓存中的值，(失效模式)

    //删除多个缓存
    @Caching(evict = {
            @CacheEvict(value = "catacory",key = "'getLevel1Category'"),
            @CacheEvict(value = "catacory",key = "'cataJSON'")
    })
    //删除catacory分区中的所有数据
//    @CacheEvict(value = "catacory",allEntries = true)
    @Override
    public void updateByCascade(PmsCategoryEntity pmsCategory) {
        boolean b = this.updateById(pmsCategory);
        //修改分类关联的表
        categoryBrandRelationService.updateCategory(pmsCategory.getCatId(),pmsCategory.getName());
    }


    /**
     * 查询所有的一级分类
     * @return
     */
    @Override
    @Cacheable(value = {"catacory"},key = "#root.methodName")
    //key：在缓存中key的名字，如果不设置那么是自动生成，
    //可以设置指定的keyname
    //也可以用方法明做key 的name
    public List<PmsCategoryEntity> getLevel1Category() {
        List<PmsCategoryEntity> parent_cid = this.baseMapper.selectList(new QueryWrapper<PmsCategoryEntity>().eq("parent_cid", 0));
        return parent_cid;
    }


    /**
     * 数据库查询三级分类(使用分布式锁的方式来解决缓存击穿)
     * @return
     */
    public Map<String,List<Catalog2Vo>> getCatalogFenBu() throws JsonProcessingException {
        String cataJSON = stringRedisTemplate.opsForValue().get("cataJSON");
        if(!StringUtils.isEmpty(cataJSON)){
            Map<String, List<Catalog2Vo>> stringListMap =  objectMapper.readValue(cataJSON, new TypeReference<>() {});
            return stringListMap;
        }
        //使用分布式锁的方式，来解决缓存击穿，去redis占坑
        //setInAbsent:如果redis为空那么就set值，如果不为空就不set
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111",20,TimeUnit.MICROSECONDS);
        if(lock){
            //加锁成功
            //如果在执行业务的时候，出现了异常，就不会走到删除锁，那么就成为死锁
            //给锁添加过期时间(30分钟)
            Map<String, List<Catalog2Vo>> cata = getCata();
            stringRedisTemplate.delete("lock");
            return cata;
        }else {
            //加锁失败，那么就是重试，加载的状态
            //休眠也可以
            return getCatalogFenBu();
        }
    }

    public Map<String,List<Catalog2Vo>> getCata() throws JsonProcessingException {
        String cataJSON = stringRedisTemplate.opsForValue().get("catacory::cataJSON");
        if(!StringUtils.isEmpty(cataJSON)){
            Map<String, List<Catalog2Vo>> stringListMap =  objectMapper.readValue(cataJSON, new TypeReference<>() {});
            return stringListMap;
        }
        //1.获取所有一级分类
        List<PmsCategoryEntity> level1Category = getLevel1Category();
        //封装数据
        //遍历一级分类,封装成为map键为一级分类的id,值为二级分类
        Map<String, List<Catalog2Vo>> parent_cid1 = level1Category.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //键为1一级分类的id,值为他的子分类
            List<PmsCategoryEntity> level2s = baseMapper.selectList(new QueryWrapper<PmsCategoryEntity>().eq("parent_cid", v.getCatId()));
            List<Catalog2Vo> collect = level2s.stream().map(level2 -> {
                Catalog2Vo catalog2Vo = new Catalog2Vo();
                catalog2Vo.setCatalog1Id(v.getCatId().toString());
                catalog2Vo.setId(level2.getCatId().toString());
                catalog2Vo.setName(level2.getName());
//                //2.寻找二级分类的三级分类封装成vo
                List<PmsCategoryEntity> level3Catalog = baseMapper.selectList(new QueryWrapper<PmsCategoryEntity>().eq("parent_cid", level2.getCatId()));
                List<Catalog2Vo.Catalog3Vo> collect1 = level3Catalog.stream().map(level3 -> {
                    Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo();
                    catalog3Vo.setName(level3.getName());
                    catalog3Vo.setId(level3.getCatId().toString());
                    catalog3Vo.setCatalog2Id(level2.getCatId().toString());
                    return catalog3Vo;
                }).collect(Collectors.toList());
                //封装成指定格式
                catalog2Vo.setCatalog3List(collect1);
                return catalog2Vo;
            }).collect(Collectors.toList());
            return collect;
        }));
        String s = JSON.toJSONString(parent_cid1);
        //设置缓存时间
        stringRedisTemplate.opsForValue().set("catacory::cataJSON",s,1, TimeUnit.DAYS);
        return parent_cid1;
    }


    /**
     * 数据库中查询三级分类(使用同步锁的方式来解决缓存击穿)
     */
    public Map<String,List<Catalog2Vo>> getCatalogDB() throws JsonProcessingException {

        synchronized (this){
            String cataJSON = stringRedisTemplate.opsForValue().get("cataJSON");
            if(!StringUtils.isEmpty(cataJSON)){
                Map<String, List<Catalog2Vo>> map = objectMapper.readValue(cataJSON, new TypeReference<>() {});
//                Map<String, List<Catalog2Vo>> stringListMap = JSON.parseObject(cataJSON, new TypeReference<Map<String, List<Catalog2Vo>>>() {
//                });
                return map;
            }
            //1.获取所有一级分类
            List<PmsCategoryEntity> level1Category = getLevel1Category();
            //封装数据
            //遍历一级分类,封装成为map键为一级分类的id,值为二级分类
            Map<String, List<Catalog2Vo>> parent_cid1 = level1Category.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
                //键为1一级分类的id,值为他的子分类
                List<PmsCategoryEntity> level2s = baseMapper.selectList(new QueryWrapper<PmsCategoryEntity>().eq("parent_cid", v.getCatId()));
                List<Catalog2Vo> collect = level2s.stream().map(level2 -> {
                    Catalog2Vo catalog2Vo = new Catalog2Vo();
                    catalog2Vo.setCatalog1Id(v.getCatId().toString());
                    catalog2Vo.setId(level2.getCatId().toString());
                    catalog2Vo.setName(level2.getName());
//                //2.寻找二级分类的三级分类封装成vo
                    List<PmsCategoryEntity> level3Catalog = baseMapper.selectList(new QueryWrapper<PmsCategoryEntity>().eq("parent_cid", level2.getCatId()));
                    List<Catalog2Vo.Catalog3Vo> collect1 = level3Catalog.stream().map(level3 -> {
                        Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo();
                        catalog3Vo.setName(level3.getName());
                        catalog3Vo.setId(level3.getCatId().toString());
                        catalog3Vo.setCatalog2Id(level2.getCatId().toString());
                        return catalog3Vo;
                    }).collect(Collectors.toList());
                    //封装成指定格式
                    catalog2Vo.setCatalog3List(collect1);
                    return catalog2Vo;
                }).collect(Collectors.toList());

                return collect;
            }));
            String s = JSON.toJSONString(parent_cid1);
            //设置缓存时间
            stringRedisTemplate.opsForValue().set("cataJSON",s,1, TimeUnit.DAYS);
            return parent_cid1;
        }


    }


    /**
     * 使用reidsLock来解决缓存击穿
     * @return
     */
    public Map<String ,List<Catalog2Vo>> redisLock() throws JsonProcessingException {
        /**
         * 1、空结果缓存：解决缓存换粗穿透
         * 2、设置过期时间(加随机值)：解决缓存雪崩
         * 3、加锁，解决缓存击穿
         */
        String cataJSON = stringRedisTemplate.opsForValue().get("cataJSON");
        if(StringUtils.isEmpty(cataJSON)){
            Map<String, List<Catalog2Vo>> catalogDB = getCatalogFenBu();
        }
        //将缓存中的json数据转换为自己想要的类型
        Map<String, List<Catalog2Vo>> stringListMap =  objectMapper.readValue(cataJSON, new TypeReference<>() {});
        return stringListMap;
    }


    /**
     * 缓存里面的数据如何和数据库的数据保持一致
     * 缓存一致性
     * 1、双写模式
     * 2、失效模式
     */


    /**
     * 使用第三方jar包实现锁，
     * @return
     */
    @Override
    public Map<String, List<Catalog2Vo>> getCatalogJson() throws JsonProcessingException {

        String cataJSON = stringRedisTemplate.opsForValue().get("catacory::cataJSON");
        if(!StringUtils.isEmpty(cataJSON)){
            Map<String, List<Catalog2Vo>> stringListMap = objectMapper.readValue(cataJSON, new TypeReference<>() {});
            return stringListMap;
        }

        //线程进来，只有释放锁，其他线程才可以进来
        //获得锁
        RLock lock = redissonClient.getLock("CataLogJSON");
        //加锁
        lock.lock();
        Map<String, List<Catalog2Vo>> cata;
        try {
            cata=getCata();
        }finally {
            //解锁
            lock.unlock();
        }
        return cata;
    }




    private List<Long> findParentPath(Long attrGroupId,List<Long> ids){

        ids.add(attrGroupId);
        System.out.println(attrGroupId);
        PmsCategoryEntity byId = this.getById(attrGroupId);
        if(byId.getParentCid()!=0){
            findParentPath(byId.getParentCid(),ids);
        }

        return ids;
    }


    /**
     * 递归查找所有菜单的子菜单
     * @param pmsCategory
     * @param
     * @return
     */
    private List<PmsCategoryEntity> getChildrens(PmsCategoryEntity pmsCategory,List<PmsCategoryEntity> PmsCategoryEntityList){

        //遍历所有的分类，获取上级分类的子分类
        List<PmsCategoryEntity> children = PmsCategoryEntityList.stream().filter(PmsCategoryEntity ->
             PmsCategoryEntity.getParentCid() == pmsCategory.getCatId()
        ).map(cata->{
            cata.setChildren(getChildrens(cata,PmsCategoryEntityList));
            return cata;
        }).sorted((menu1,menu2)->{
            return( menu1.getSort()==null?0: menu1.getSort())-(menu2.getSort()==null?0:menu2.getSort());
        }).collect(Collectors.toList());
        return children;
    }

}